package cartagows;

import java.io.FileInputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.Vector;

import javax.wsdl.Definition;
import javax.wsdl.Input;
import javax.wsdl.Operation;
import javax.wsdl.OperationType;
import javax.wsdl.Output;
import javax.wsdl.Service;
import javax.wsdl.factory.WSDLFactory;
import javax.wsdl.xml.WSDLReader;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.om.OMXMLParserWrapper;
import org.apache.axiom.om.impl.llom.factory.OMXMLBuilderFactory;
import org.apache.axiom.om.impl.llom.util.AXIOMUtil;
import org.apache.axiom.soap.SOAPBody;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.SOAPFactory;
import org.apache.axis2.AxisFault;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.saaj.util.SAAJUtil;
import org.apache.axis2.util.XMLUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.neethi.All;
import org.apache.neethi.Constants;
import org.apache.neethi.ExactlyOne;
import org.apache.neethi.Policy;
import org.apache.neethi.PolicyEngine;
import org.apache.neethi.PolicyReference;
import org.apache.ws.security.message.WSSecHeader;
import org.w3c.dom.Document;

import cartago.ARTIFACT_INFO;
import cartago.Artifact;
import cartago.CartagoException;
import cartago.GUARD;
import cartago.IBlockingCmd;
import cartago.INTERNAL_OPERATION;
import cartago.OPERATION;
import cartago.OUTPORT;
import cartago.OpFeedbackParam;
import cartagows.util.SOAPUtil;
import cartagows.util.XMLib;
import cartagows.wsframework.core.DataContainer;
import cartagows.wsframework.core.IOperation;
import cartagows.wsframework.core.ServiceConfiguration;
import cartagows.wsframework.wsaddressing.WSAddressingParam;
import cartagows.wsframework.wssecurity.core.AsymmetricBindingBuilder;
import cartagows.wsframework.wssecurity.core.MessageData;
import cartagows.wsframework.wssecurity.core.SecurityException;
import cartagows.wsframework.wssecurity.core.SecurityInfo;
import cartagows.wsframework.wssecurity.core.ValidationResults;
import cartagows.wsframework.wssecurity.core.WSSecurityOperation;
import cartagows.wsframework.wssecurity.core.WSSecurityOperationContent;
import cartagows.wsframework.wssecurity.policy.PolicyBuilder;
import cartagows.wsframework.wssecurity.policy.PolicyData;
import cartagows.wsframework.wssecurity.policy.WSSPolicyException;
import cartagows.wsframework.wssecurity.utils.SecurityUtil;

import com.ibm.wsdl.PortImpl;
import com.ibm.wsdl.extensions.soap.SOAPBindingImpl;
 
@ARTIFACT_INFO(
		outports = {
			@OUTPORT(name = "out-port")
		}
	)public class WSPanel extends Artifact {

	private static final Log log = LogFactory.getLog(WSPanel.class);

	private LinkedList<MsgInfo> items;
	private Definition def;
	
	private EndpointReference targetEPR;
	private EndpointReference securityTargetEPR;

	/**
	 * Stores the operations provided by the WS:
	 * 
	 *  - HashMap key: The action related to the provided operation.
	 *    In the notifyMsg operation this key is used to check if the
	 *    arrived message is supported by the WS
	 *  - HashMap value: An entry containing the operation name and the
	 *    message name (used as a unique wrapper of the message contents
	 *    in case of xxxWithParts operations)
	*/
	private HashMap<String,MsgOpMapEntry> msgOpMap;
	private HashMap<String,OpInOutMsgMapEntry> opInOutMsgMap;
	private SOAPFactory factory = OMAbstractFactory.getSOAP12Factory();

	private HashMap<String,PolicyData> policyRequestDataMap;
	private HashMap<String,PolicyData> policyResponseDataMap;
	private Vector wsResult;


	@OPERATION void init(String wsdlURI, String serviceName) throws Exception {
		//String realWsdlURI = CWSServiceManager.CARTAGO_WS_BASE_PATH + wsdlURI;
		WSDLFactory wsdlfactory = WSDLFactory.newInstance();
		WSDLReader wsdlreader = wsdlfactory.newWSDLReader();
		Map portsMap = null;
		//def = wsdlreader.readWSDL(realWsdlURI);			
		def = wsdlreader.readWSDL(wsdlURI);
		msgOpMap = new HashMap<String, WSPanel.MsgOpMapEntry>();
		opInOutMsgMap = new HashMap<String, WSPanel.OpInOutMsgMapEntry>();
		policyRequestDataMap = new HashMap<String, PolicyData>();
		policyResponseDataMap = new HashMap<String, PolicyData>();
		wsResult = null;


		log.info("WSPanel Init - Parsing WSDL for service "+def.getQName());

		//Research of the service specified in input
		Collection services = def.getAllServices().values();
		for (Object obj: services){
			Service srv = (Service)obj;
			if (srv.getQName().getLocalPart().equals(serviceName)){
				portsMap = srv.getPorts();
				break;
			}
		}
		//Service not found
		if(portsMap==null)
			throw new Exception("No ports find for the given service");
		//Service found
		else{

			String opInAction;
			String opOutAction;
			QName opInMsgName;
			QName opOutMsgName;
			Input in;
			Output out;

			boolean isBindingRpc = false;
			//Parsing the ports
			for (Object obj: portsMap.values()){
	    		PortImpl  pt = (PortImpl)obj;

				log.info("Port Type: "+pt.getBinding().getPortType());
				
				//Founding the binding type
				for (Object el : pt.getBinding().getExtensibilityElements()){
					if (el instanceof SOAPBindingImpl){
						SOAPBindingImpl bindingImpl = (SOAPBindingImpl) el;
						if(bindingImpl.getStyle().equals(CartagowsParam.BINDING_RPC)) isBindingRpc = true;
						break;
					}
				}
						
				//Parsing the ports operations
				for (Object obj1: pt.getBinding().getPortType().getOperations()){
					Operation op = (Operation)obj1;
					log.info("Op: "+op.getName());
					
	    			/** 
	    			  * Secondo specifiche le action per i messaggi relativi a WS-AtomicTransaction 
	    			  * devono avere una precisa struttura e pertanto viene costruita manulmente.
	    			  * (il namespace cioe' deve rimanere http://docs.oasis-open.org/ws-tx/wsat/2006/06)
	    			  * 
	    			**/ 
					String base =null;
					if(op.getName().equals("PrepareOperation") ||op.getName().equals("CommitOperation") || op.getName().equals("RollbackOperation") || op.getName().equals("AbortedOperation") || op.getName().equals("CommittedOperation") || op.getName().equals("PreparedOperation") || op.getName().equals("ReadOnlyOperation"))
	    				base = "http://docs.oasis-open.org/ws-tx/wsat/2006/06"+"/";
	    			else
	    				base = def.getTargetNamespace()+"/"+pt.getBinding().getPortType().getQName().getLocalPart()+"/";
	    			
					
					
					/*
					 * Construction of the operation's action in conformance with the WS-Adrressing specification
					 * In the abce of the wsa:Action attribute, the following pattern is used 
					 * to construct a default action for inputs and outputs. The general form of an action URI is as follows:
					 * 
					 * [target namespace]/[port type name]/[input|output name]
					 * 
					 * where [input|output name] it is a parameter which provides a unique name among all input and output elements within the enclosing port type.
					 * 
					 * In order to avoid having to name each input and output element within an operation, WSDL provides some default values based on the operation name. 
					 * If the name attribute is not specified on a one-way or notification message, it defaults to the name of the operation. If the name attribute is 
					 * not specified on the input or output messages of a request-response or solicit-response operation, the name defaults to the name of the operation with 
					 * "Request"/"Solicit" or "Response" appended, respectively. 
					 */

					if (op.getStyle().equals(OperationType.ONE_WAY)){
						
						in = op.getInput();

						/*
						 * Retrieval of the messageInputName (the root of the SOAP message body
						 * related to the operation in case of message sent with parts)
						*/
						opInMsgName = getInOpMsgName(isBindingRpc, op);
						
						if (in.getName()!=null){
							opInAction = base+in.getName();
							msgOpMap.put(opInAction, new MsgOpMapEntry(op.getName(), opInMsgName));
							//log.info("link: "+base+in.getName()+"-->"+op.getName());
						} else {
							opInAction = base+op.getName();
							msgOpMap.put(opInAction, new MsgOpMapEntry(op.getName(), opInMsgName));
							//log.info("link: "+base+op.getName()+"-->"+op.getName());
						}
						opInOutMsgMap.put(op.getName(), new OpInOutMsgMapEntry(opInAction, null));
					} else if (op.getStyle().equals(OperationType.REQUEST_RESPONSE)){

						in = op.getInput();
						out = op.getOutput();
						/*
						 * Retrieval of the messageInputName (the root of the SOAP message body
						 * related to the operation in case of message sent with parts)
						*/
						opInMsgName = getInOpMsgName(isBindingRpc, op);
						/*
						 * Retrieval of the messageOutputName (the root of the SOAP message body
						 * related to the operation in case of message sent with parts)
						*/
						opOutMsgName = getOutOpMsgName(isBindingRpc, op);
						
						if (in.getName()!=null){
							opInAction = base+in.getName();
							msgOpMap.put(opInAction, new MsgOpMapEntry(op.getName(), opInMsgName));
							//log.info("link: "+base+in.getName()+"-->"+op.getName());
						} else {
							opInAction = base+op.getName()+"Request";
							msgOpMap.put(opInAction, new MsgOpMapEntry(op.getName(), opInMsgName));
							//log.info("link: "+base+op.getName()+"Request-->"+op.getName());
						}
						if (out.getName()!=null){
							opOutAction = base+out.getName();
							msgOpMap.put(opOutAction, new MsgOpMapEntry(op.getName(), opOutMsgName));
							//log.info("link: "+base+out.getName()+"<--"+op.getName());
						} else {
							opOutAction = base+op.getName()+"Response";
							msgOpMap.put(opOutAction, new MsgOpMapEntry(op.getName(), opOutMsgName));
							//log.info("link: "+base+op.getName()+"Response<--"+op.getName());
						}
						opInOutMsgMap.put(op.getName(), new OpInOutMsgMapEntry(opInAction, opOutAction));
					} else if (op.getStyle().equals(OperationType.NOTIFICATION)){
						
						out = op.getOutput();
						//Retrieval of the messageOutputName (the root of the SOAP message body related to the operation)
						opOutMsgName = getOutOpMsgName(isBindingRpc, op);
						
						if (out.getName()!=null){
							opOutAction = base+out.getName();
							msgOpMap.put(opOutAction, new MsgOpMapEntry(op.getName(), opOutMsgName));
							//log.info("link: "+base+out.getName()+"<--"+op.getName());
						} else {
							opOutAction = base+op.getName();
							msgOpMap.put(opOutAction, new MsgOpMapEntry(op.getName(), opOutMsgName));
							//log.info("link: "+base+op.getName()+"<--"+op.getName());
						}
						opInOutMsgMap.put(op.getName(), new OpInOutMsgMapEntry(null, opOutAction));
					} else if (op.getStyle().equals(OperationType.SOLICIT_RESPONSE)){
						// TODO
					}
				}
			}
		}
		items = new LinkedList<MsgInfo>();
		//parseSecInfo(realWsdlURI);
		parseSecInfo(wsdlURI);
	}

	@OPERATION void subscribeWSMsgsWithFilter(IWSMsgFilter filter){
		execInternalOp("notifySubscriberWithFilter",filter);
	}

	@INTERNAL_OPERATION(guard="itemAvailableWithFilter") void notifySubscriberWithFilter(IWSMsgFilter filter){
		Iterator<MsgInfo> it = items.listIterator();
		while  (it.hasNext()){
			WSMsgInfo item = it.next().getWSMsgInfo();
			if (filter.match(item)){
				it.remove();
				log("new msg with filter found");
				signal("msg",item);
				break;
			}
		}
		execInternalOp("notifySubscriberWithFilter",filter);
		//log("signaled.");
	}

	@OPERATION void subscribeWSMsgs(){
		execInternalOp("notifySubscriber");
	}

	@INTERNAL_OPERATION(guard="itemAvailable") void notifySubscriber(){
		//log("new msg available.");
		MsgInfo item = items.removeFirst();
		signal("msg",item.getWSMsgInfo());
		execInternalOp("notifySubscriber");
		//log("signaled.");
	}

	@OPERATION(guard="itemAvailable") void getWSMsg(OpFeedbackParam<WSMsgInfo> res) throws CartagoException{
		//log("new msg available.");
		MsgInfo item = items.removeFirst();
		targetEPR = new EndpointReference(item.getWSMsgInfo().getWSAReplyToAddress());
		securityTargetEPR = targetEPR;
		PolicyData pd = policyRequestDataMap.get(item.getWSMsgInfo().getOperationName());
		if(pd!=null && SecurityUtil.isSecHeaderRequired(pd)){
			ValidationResults validationRes = validateMsg(item, pd);
			WSMsgInfo msgValidated = validationRes.getMsg();
			wsResult = validationRes.getWsResults();
			res.set(msgValidated);
		} else {
			res.set(item.getWSMsgInfo());
		}	
		
		//log("signaled.");
	}

	@OPERATION(guard="itemAvailableWithFilter") void getWSMsgWithFilter(IWSMsgFilter filter, OpFeedbackParam<WSMsgInfo> res) throws CartagoException{
		Iterator<MsgInfo> it = items.listIterator();
		while  (it.hasNext()){
			MsgInfo item = it.next();
			if (filter.match(item.getWSMsgInfo())){
				targetEPR = new EndpointReference(item.getWSMsgInfo().getWSAReplyToAddress());
				securityTargetEPR = targetEPR;
				PolicyData pd = policyRequestDataMap.get(item.getWSMsgInfo().getOperationName());
				if(pd!=null && SecurityUtil.isSecHeaderRequired(pd)){
					ValidationResults validationRes = validateMsg(item, pd);
					WSMsgInfo msgValidated = validationRes.getMsg();
					wsResult = validationRes.getWsResults();
					res.set(msgValidated);
				} else {
					res.set(item.getWSMsgInfo());
				}	
				it.remove();
				break;
			}
		}
	}

	@GUARD boolean itemAvailable(OpFeedbackParam<WSMsgInfo> res){
		return items.size() > 0;
	}

	@GUARD boolean itemAvailable(){
		return items.size() > 0;
	}

	@GUARD boolean itemAvailableWithFilter(IWSMsgFilter filter){
		for (MsgInfo ctx: items){
			if (filter.match(ctx.getWSMsgInfo())){
				return true;
			}
		}
		return false;
	}

	@GUARD boolean itemAvailableWithFilter (IWSMsgFilter filter, OpFeedbackParam<WSMsgInfo> res){
		return this.itemAvailableWithFilter(filter);
	}
							   
	@OPERATION void sendWSReply(WSMsgInfo ctx, String reply, ServiceConfiguration config, OpFeedbackParam<String> replyId) throws SecurityException, CartagoException{
		try {
			targetEPR = new EndpointReference(ctx.getWSAReplyToAddress());
			final ServiceClient sender = new ServiceClient();
			String newMsgBody = reply;
			
			Document doc = null;
			PolicyData pd = policyResponseDataMap.get(ctx.getOperationName());
			if(pd!=null && SecurityUtil.isSecHeaderRequired(pd)){
				String SOAPMSG = 
					"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" 
					+ "<soapenv:Envelope "
					+   "xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" "
					+   "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" "
					+   "xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" "
					+   "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" 
					+   "<soapenv:Body wsu:Id=\"Body-1\">" 
					+       reply
					+   "</soapenv:Body>" 
					+ "</soapenv:Envelope>";
				doc = null;
				try {
					doc = SOAPUtil.toSOAPPart(SOAPMSG);
				} catch (Exception e) {
					e.printStackTrace();
				}
				OMElement header = null;
				try {
					header = createSecurityHeader(doc,pd);
				} catch (SecurityException e) {
					e.printStackTrace();
				}
				sender.addHeader(header);
				SOAPEnvelope env = SAAJUtil.getSOAPEnvelopeFromDOOMDocument(doc);
				SOAPBody body = env.getBody();
				newMsgBody = body.toString();
			}

			String toAddress = ctx.getWSAReplyToAddress();
			if (toAddress!=null){
				Options options = new Options();

				sender.setOptions(options);

				UUID id = UUID.randomUUID();
				String idString = id.toString();

				//EndpointReference targetEPR = new EndpointReference(toAddress);
				options.setTo(targetEPR);

				OMFactory fac = OMAbstractFactory.getOMFactory();
				OMNamespace omNs = fac.createOMNamespace(WSAddressingParam.NAMESPACE, WSAddressingParam.NAMESPACE_PREFIX);

				String msgId = ctx.getWSAMessageId();
				OMElement relates = fac.createOMElement("RelatesTo", omNs);
				relates.setText(msgId);
				sender.addHeader(relates);

				OMElement to = fac.createOMElement("To", omNs);
				to.setText(toAddress);
				sender.addHeader(to);

				OMElement nmsgId = fac.createOMElement("MessageID", omNs);
				nmsgId.setText(idString);
				sender.addHeader(nmsgId);
				String actionValue = opInOutMsgMap.get(ctx.getOperationName()).getOutAction();
				ctx.getWSAAction();
				options.setAction("");
				OMElement actionNode = fac.createOMElement("Action", omNs);
				actionNode.setText(actionValue);
				sender.addHeader(actionNode);
				
				//Adding additional header information
				if(config!=null){
					OMElement[] headersList = config.getConfig();
					for(int i=0;i<headersList.length;i++){
						sender.addHeader(headersList[i]);
					}
				} 

				try {
					final OMElement el = AXIOMUtil.stringToOM(newMsgBody);
					await(new IBlockingCmd(){
						public void exec(){
							try {
								sender.sendRobust(el);
							} catch (AxisFault e) {
								e.printStackTrace();
							}	
						}
					});
					replyId.set(idString);
				} catch (Exception ex){
					ex.printStackTrace();
					log.info("reply failed for "+msgId);
					failed(ex.toString());
				}
			} else {
				/*
					MessageContext inMsgContext = ctx.getContext();
					MessageContext outMsgContext = MessageContextBuilder.createOutMessageContext(inMsgContext);
			        outMsgContext.getOperationContext().addMessageContext(outMsgContext);
			        //log.info("out msg ctx creation ok");
			        SOAPFactory fac = getSOAPFactory(ctx.getContext());
			        SOAPEnvelope envelope = fac.getDefaultEnvelope();

					OMElement action = ctx.getHeaderElement("Action");
			        if (action!=null){
			        	OMNamespace omNs = fac.createOMNamespace("http://www.w3.org/2005/08/addressing", "wsa");
			        	OMElement wsact = fac.createOMElement("Action", omNs);
			        	wsact.setText(action.getText());
			        	outMsgContext.setSoapAction(action.getText());
			        	envelope.getHeader().addChild(wsact);
			        }
			        envelope.getBody().addChild(reply);
			        outMsgContext.setEnvelope(envelope);
			        //log.info("soap envelope creation ok");
			        //log.info(envelope.toString());
			        AxisEngine engine =new AxisEngine(inMsgContext.getConfigurationContext());
			        engine.send(outMsgContext);
			        signal("reply_succeeded");*/
				log.info("reply failed: NO WSAddressing");
				failed("reply_failed, NO-WSAddressing");
			} 
		} catch (AxisFault ex){
			failed(ex.toString());
		}
	}

	
	@OPERATION void sendWSReply(WSMsgInfo ctx, String reply, OpFeedbackParam<String> replyId) throws Exception{
		this.sendWSReply(ctx, reply, null, replyId);
	}

	//For messages with multiple parts
	@OPERATION void sendWSReplyWithParts(WSMsgInfo ctx, OpFeedbackParam<String> replyId, Object[] replyParts){
		this.sendWSReplyWithParts(ctx, replyId, null, replyParts);
	}

	//For messages with multiple parts
	@OPERATION void sendWSReplyWithParts(WSMsgInfo ctx, OpFeedbackParam<String> replyId, ServiceConfiguration config, Object[] replyParts){

		try{
			String actionValue = opInOutMsgMap.get(ctx.getOperationName()).getOutAction();
			QName replyMsgName = msgOpMap.get(actionValue).getOpMsgName();
			//Message body construction: first creation of the wrapper with the message name
			OMElement contentWrapper = XMLib.getInstance().buildElement(replyMsgName);

			for(Object part : replyParts) contentWrapper.addChild(XMLib.getInstance().buildElementFromString((String)part));
			this.sendWSReply(ctx, contentWrapper.toString(), config, replyId);
		}
		catch(Exception ex){
			ex.printStackTrace();
			failed(ex.toString());
		}
	}

	@OPERATION void notifyMsg(MsgInfo msg){
		String action = msg.getWSAAction();
		log.info("[WS-PANEL] Msg notified: "+action+"\n"+msg.getEnvelope().toString());
		//log.info("notifyMsg: Looking for the op for "+action);
		MsgOpMapEntry entry = msgOpMap.get(action);
		if (entry!=null){
			msg.setOpName(entry.getOpName());
			items.add(msg);
			//log("INSERTED NEW ITEM "+opName);
		} else {
			failed("msg_unknown");
		}
	}

	@OPERATION void setSecurityTargetEPR(String securityTargetEPR){
		this.securityTargetEPR = new EndpointReference(securityTargetEPR);
	}

	private void parseSecInfo(String wsdlURI){
		Policy policyService = null;
		Policy policyEndpoint = null;
		Policy policyOperation = null;
		Policy policyMessage = null;
		HashMap<String, Policy> messagePolicyMap = new HashMap<String, Policy>();
		XMLStreamReader reader = null;
		try {
			reader = XMLInputFactory.newInstance().createXMLStreamReader(new FileInputStream(wsdlURI));
		} catch (Exception e) {
			e.printStackTrace();
		}
		OMXMLParserWrapper builder = OMXMLBuilderFactory.createStAXOMBuilder(OMAbstractFactory.getOMFactory(),reader);

		OMElement element = builder.getDocumentElement();

		Iterator it = element.getChildElements();
		while(it.hasNext()){
			OMElement el = (OMElement) it.next();
			if(el.getLocalName().equals("service")){
				Iterator it2 = el.getChildrenWithName(new QName(Constants.URI_POLICY_NS, Constants.ELEM_POLICY_REF));
				while(it2.hasNext()){
					OMElement ref = (OMElement) it2.next();
					PolicyReference policyRef = PolicyEngine.getPolicyReference(ref);
					Policy policy = null; 
					Iterator it3  = builder.getDocumentElement().getChildrenWithName(new QName(Constants.URI_POLICY_NS, Constants.ELEM_POLICY)); 
					while(it3.hasNext()){
						policy = PolicyEngine.getPolicy((OMElement)it3.next());
						if(policy.getId().equals(policyRef.getURI().substring(1, policyRef.getURI().length()))){
							if(policyService == null){
								policyService = policy;
							} else {
								policyService = policyService.merge(policy);
							}
							break;
						}
					}
				}
				break;
			}
		}
		it = element.getChildElements();
		while(it.hasNext()){
			OMElement el = (OMElement) it.next();
			if(el.getLocalName().equals("binding")){
				Iterator it2 = el.getChildrenWithName(new QName(Constants.URI_POLICY_NS, Constants.ELEM_POLICY_REF));
				while(it2.hasNext()){
					OMElement ref = (OMElement) it2.next();
					PolicyReference policyRef = PolicyEngine.getPolicyReference(ref);
					Policy policy = null; 
					Iterator it3  = builder.getDocumentElement().getChildrenWithName(new QName(Constants.URI_POLICY_NS, Constants.ELEM_POLICY)); 
					while(it3.hasNext()){
						policy = PolicyEngine.getPolicy((OMElement)it3.next());
						if(policy.getId().equals(policyRef.getURI().substring(1, policyRef.getURI().length()))){
							if(policyEndpoint == null){
								policyEndpoint = policy;
							} else {
								policyEndpoint = policyEndpoint.merge(policy);
							}
							break;
						}
					}
				}
				Iterator it3 = el.getChildElements();
				while(it3.hasNext()){
					OMElement op = null;
					OMElement elemInsideBinding = (OMElement) it3.next();
					if(elemInsideBinding.getLocalName().equals("operation")){
						policyOperation = null;
						op = (OMElement) elemInsideBinding;
						Iterator it4 = op.getChildrenWithName(new QName(Constants.URI_POLICY_NS, Constants.ELEM_POLICY_REF));
						while(it4.hasNext()){
							OMElement ref = (OMElement) it4.next();
							PolicyReference policyRef = PolicyEngine.getPolicyReference(ref);
							Policy policy = null; 
							Iterator it5  = builder.getDocumentElement().getChildrenWithName(new QName(Constants.URI_POLICY_NS, Constants.ELEM_POLICY)); 
							while(it5.hasNext()){
								policy = PolicyEngine.getPolicy((OMElement)it5.next());
								if(policy.getId().equals(policyRef.getURI().substring(1, policyRef.getURI().length()))){
									if(policyOperation == null){
										policyOperation = policy;
									} else {
										policyOperation = policyOperation.merge(policy);
									}

									break;
								}
							}
						}
					}
					Iterator it6 = elemInsideBinding.getChildElements();
					while(it6.hasNext()){
						OMElement elemInsideOperation = (OMElement) it6.next();
						if(elemInsideOperation.getLocalName().equals("input")){
							policyMessage = null;
							Iterator it7 = elemInsideOperation.getChildrenWithName(new QName(Constants.URI_POLICY_NS, Constants.ELEM_POLICY_REF));
							while(it7.hasNext()){
								OMElement ref = (OMElement) it7.next();
								PolicyReference policyRef = PolicyEngine.getPolicyReference(ref);
								Policy policy = null; 
								Iterator it8  = builder.getDocumentElement().getChildrenWithName(new QName(Constants.URI_POLICY_NS, Constants.ELEM_POLICY)); 
								while(it8.hasNext()){
									policy = PolicyEngine.getPolicy((OMElement)it8.next());
									if(policy.getId().equals(policyRef.getURI().substring(1, policyRef.getURI().length()))){
										if(policyMessage == null){
											policyMessage = policy;
										} else {
											policyMessage = policyMessage.merge(policy);
										}
										break;
									}
								}
							}
							if(policyMessage == null){
								if(policyService != null && policyEndpoint != null && policyOperation != null){
									policyMessage = policyService;
									policyMessage = policyMessage.merge(policyEndpoint);
									policyMessage = policyMessage.merge(policyOperation);
								} else if(policyService != null && policyEndpoint != null && policyOperation == null){
									policyMessage = policyService;
									policyMessage = policyMessage.merge(policyEndpoint);
								} else if(policyService != null && policyEndpoint == null && policyOperation != null){
									policyMessage = policyService;
									policyMessage = policyMessage.merge(policyOperation);
								} else if(policyService == null && policyEndpoint != null && policyOperation != null){
									policyMessage = policyEndpoint;
									policyMessage = policyMessage.merge(policyOperation);
								} else if(policyService != null && policyEndpoint == null && policyOperation == null){
									policyMessage = policyService;
								} else if(policyService == null && policyEndpoint != null && policyOperation == null){
									policyMessage = policyEndpoint;
								} else if(policyService == null && policyEndpoint == null && policyOperation != null){
									policyMessage = policyOperation;
								}
							} else if(policyMessage != null){
								if(policyService != null && policyEndpoint != null && policyOperation != null){
									policyMessage = policyService.merge(policyEndpoint).merge(policyOperation).merge(policyOperation);
								} else if(policyService != null && policyEndpoint != null && policyOperation == null){
									policyMessage = policyService.merge(policyEndpoint).merge(policyMessage);
								} else if(policyService != null && policyEndpoint == null && policyOperation != null){
									policyMessage = policyService.merge(policyOperation).merge(policyMessage);
								} else if(policyService == null && policyEndpoint != null && policyOperation != null){
									policyMessage = policyEndpoint.merge(policyOperation).merge(policyMessage);
								} else if(policyService != null && policyEndpoint == null && policyOperation == null){
									policyMessage = policyService.merge(policyMessage);
								} else if(policyService == null && policyEndpoint != null && policyOperation == null){
									policyMessage = policyEndpoint.merge(policyMessage);
								} else if(policyService == null && policyEndpoint == null && policyOperation != null){
									policyMessage = policyOperation.merge(policyMessage);
								}
							}
							messagePolicyMap.put(op.getAttributeValue(new QName("name"))+"Request", policyMessage);
						}
						if(elemInsideOperation.getLocalName().equals("output")){
							policyMessage = null;
							Iterator it7 = elemInsideOperation.getChildrenWithName(new QName(Constants.URI_POLICY_NS, Constants.ELEM_POLICY_REF));
							while(it7.hasNext()){
								OMElement ref = (OMElement) it7.next();
								PolicyReference policyRef = PolicyEngine.getPolicyReference(ref);
								Policy policy = null; 
								Iterator it8  = builder.getDocumentElement().getChildrenWithName(new QName(Constants.URI_POLICY_NS, Constants.ELEM_POLICY)); 
								while(it8.hasNext()){
									policy = PolicyEngine.getPolicy((OMElement)it8.next());
									if(policy.getId().equals(policyRef.getURI().substring(1, policyRef.getURI().length()))){
										if(policyMessage == null){
											policyMessage = policy;
										} else {
											policyMessage = policyMessage.merge(policy);
										}
										break;
									}
								}
							}
							if(policyMessage == null){
								if(policyService != null && policyEndpoint != null && policyOperation != null){
									policyMessage = policyService;
									policyMessage = policyMessage.merge(policyEndpoint);
									policyMessage = policyMessage.merge(policyOperation);
								} else if(policyService != null && policyEndpoint != null && policyOperation == null){
									policyMessage = policyService;
									policyMessage = policyMessage.merge(policyEndpoint);
								} else if(policyService != null && policyEndpoint == null && policyOperation != null){
									policyMessage = policyService;
									policyMessage = policyMessage.merge(policyOperation);
								} else if(policyService == null && policyEndpoint != null && policyOperation != null){
									policyMessage = policyEndpoint;
									policyMessage = policyMessage.merge(policyOperation);
								} else if(policyService != null && policyEndpoint == null && policyOperation == null){
									policyMessage = policyService;
								} else if(policyService == null && policyEndpoint != null && policyOperation == null){
									policyMessage = policyEndpoint;
								} else if(policyService == null && policyEndpoint == null && policyOperation != null){
									policyMessage = policyOperation;
								}
							} else if(policyMessage != null){
								if(policyService != null && policyEndpoint != null && policyOperation != null){
									policyMessage = policyService.merge(policyEndpoint).merge(policyOperation).merge(policyMessage);
								} else if(policyService != null && policyEndpoint != null && policyOperation == null){
									policyMessage = policyService.merge(policyEndpoint).merge(policyMessage);
								} else if(policyService != null && policyEndpoint == null && policyOperation != null){
									policyMessage = policyService.merge(policyOperation).merge(policyMessage);
								} else if(policyService == null && policyEndpoint != null && policyOperation != null){
									policyMessage = policyEndpoint.merge(policyOperation).merge(policyMessage);
								} else if(policyService != null && policyEndpoint == null && policyOperation == null){
									policyMessage = policyService.merge(policyMessage);
								} else if(policyService == null && policyEndpoint != null && policyOperation == null){
									policyMessage = policyEndpoint.merge(policyMessage);
								} else if(policyService == null && policyEndpoint == null && policyOperation != null){
									policyMessage = policyOperation.merge(policyMessage);
								}
							}
							messagePolicyMap.put(op.getAttributeValue(new QName("name"))+"Response", policyMessage);

						}
					}
				}
			}
		}
		Set list = msgOpMap.keySet();
		for(Object key:list){
			String opValue = msgOpMap.get(key).getOpName();
			Policy policy = null;
			try {
				policy = messagePolicyMap.get(opValue+"Request");
			} catch (Exception e) {
				e.printStackTrace();
			}
			if(policy!=null){
				policy = (Policy) policy.normalize(true);
				ExactlyOne eo =  (ExactlyOne) policy.getFirstPolicyComponent();
				List listOfPolicyAlternatives = eo.getPolicyComponents();
				All aPolicyAlternative = (All) listOfPolicyAlternatives.get(0);
				List listOfAssertions = null;
				listOfAssertions = aPolicyAlternative.getAssertions();
				PolicyData policyData = null;
				try {
					policyData = PolicyBuilder.build(listOfAssertions);
				} catch (WSSPolicyException e) {
					e.printStackTrace();
				}
				policyRequestDataMap.put((String) opValue, policyData);
			}
			try {
				policy = messagePolicyMap.get(opValue+"Response");
			} catch (Exception e) {
				e.printStackTrace();
			}
			if(policy!=null){
				policy = (Policy) policy.normalize(true);
				ExactlyOne eo =  (ExactlyOne) policy.getFirstPolicyComponent();
				List listOfPolicyAlternatives = eo.getPolicyComponents();
				All aPolicyAlternative = (All) listOfPolicyAlternatives.get(0);
				List listOfAssertions = null;
				listOfAssertions = aPolicyAlternative.getAssertions();
				PolicyData policyData = null;
				try {
					policyData = PolicyBuilder.build(listOfAssertions);
				} catch (WSSPolicyException e) {
					e.printStackTrace();
				}
				policyResponseDataMap.put((String) opValue, policyData);
			}
		}
	}

	private OMElement createSecurityHeader(Document doc, PolicyData pd) throws SecurityException, CartagoException{

		//SecurityHeader
		WSSecHeader secHeader = new WSSecHeader();
		secHeader.setMustUnderstand(false);
		secHeader.insertSecurityHeader(doc);

		SecurityInfo securityConfig = getSecurityInfo();
    	MessageData md = new MessageData(pd,doc,secHeader,securityConfig,wsResult,false);

		//TransportBinding
		if(pd.isTransportBinding()) {
			log.debug("Building transport binding");
			//TransportBindingBuilder building = new TransportBindingBuilder();
			//building.build(md);

		//SymmericBinding
		} else if(pd.isSymmetricBinding()) {
			//log.debug("Building SymmetricBinding");
			//SymmetricBindingBuilder builder = new SymmetricBindingBuilder();
			//builder.build(md);

		//AsymmetricBinding
		} else {
			AsymmetricBindingBuilder builder = new AsymmetricBindingBuilder();
			try {
				builder.build(md);
			} catch (SecurityException e) {
				e.printStackTrace();
			}
		}
		OMElement header = null;
		try {
			header = XMLUtils.toOM(secHeader.getSecurityHeader());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return header;
	}
	
	private ValidationResults validateMsg(MsgInfo msg, PolicyData pd) throws CartagoException{
		OpFeedbackParam<ValidationResults> res = new OpFeedbackParam<ValidationResults>();
		if(pd !=null && SecurityUtil.isSecHeaderRequired(pd)){
			WSSecurityOperation operation = new WSSecurityOperation(WSSecurityOperation.VALIDATE_MSG);
			WSSecurityOperationContent opContent = new WSSecurityOperationContent(pd, false);
			try {
	    		execLinkedOp("out-port","validateMessage",targetEPR.getAddress(), msg.getWSMsgInfo(), operation, new DataContainer(DataContainer.OPERATION_SPECIFIC, opContent), res);
	    	} catch (Exception ex){
	    		ex.printStackTrace();
	    	}
		}
		return res.get();
	}
	
	private SecurityInfo getSecurityInfo() throws CartagoException{
		OpFeedbackParam<HashMap<String, DataContainer>> res = new OpFeedbackParam<HashMap<String,DataContainer>>();
		
    	execLinkedOp("out-port","getWalletInfo",securityTargetEPR.getAddress(),new Integer(IOperation.WS_SECURITY), res);
   
    	HashMap<String, DataContainer> resSecurity = res.get();
    	Set list = resSecurity.keySet();

    	SecurityInfo securityInfo = null;
    	for(Object key:list){
			DataContainer value = resSecurity.get(key);
			securityInfo = (SecurityInfo) value.getData();
			break;
		}
		return securityInfo;
	}
	
	class MsgOpMapEntry{

		private String opName;
		private QName opMsgName;
		
		public MsgOpMapEntry(String opName, QName opMsgName) {
			this.opName = opName;
			this.opMsgName = opMsgName;
		}

		public String getOpName() {
			return opName;
		}

		public QName getOpMsgName() {
			return opMsgName;
		}
	}
	
	class OpInOutMsgMapEntry{
		private String inAction;
		private String outAction;

		public OpInOutMsgMapEntry(String inAction, String outAction) {
			this.inAction = inAction;
			this.outAction = outAction;
		}

		public String getInAction() {
			return inAction;
		}

		public String getOutAction() {
			return outAction;
		}
	}
	
	/** 
	 * From the WSDL specification:
	 * 
	 * The soap:body element specifies how the message parts appear inside the SOAP Body element.
	 * 
	 * The parts of a message may either be abstract type definitions, or concrete schema definitions. If abstract definitions,
	 *  the types are serialized according to some set of rules defined by an encoding style. 
	 *  Each encoding style is identified using a list of URIs, as in the SOAP specification.
	 *  The soap:body binding element provides information on how to assemble the different message parts inside the Body element of the SOAP message. The soap:body element is used in both RPC-oriented and document-oriented messages, but the style of the enclosing operation has important effects on how the Body section is structured:
	 *  	- If the operation style is rpc each part is a parameter or a return value and appears inside a wrapper element within 
	 *        the body (following Section 7.1 of the SOAP specification). 
	 *        The wrapper element is named identically to the operation name and its namespace is the value of the namespace attribute. 
	 *        Each message part (parameter) appears under the wrapper, represented by an accessor named identically to the corresponding 
	 *        parameter of the call. Parts are arranged in the same order as the parameters of the call.
	 *     -  If the operation style is document there are no additional wrappers, and the message parts appear directly under the SOAP Body
	 *        element. 
	 * 
	 * So, for providing the name for the wrapper we use the convention related to the operations names:
	 * 
	 *  The name attribute of the input and output elements provides a unique name among all input and output elements 
	 *  within the enclosing port type. In order to avoid having to name each input and output element within an operation, 
	 *  WSDL provides some default values based on the operation name. If the name attribute is not specified on a one-way or 
	 *  notification message, it defaults to the name of the operation. If the name attribute is not specified on the input 
	 *  or output messages of a request-response or solicit-response operation, the name defaults to the name of the operation 
	 *  with "Request"/"Solicit" or "Response" appended, respectively.
	 */
	
	private QName getInOpMsgName(boolean isBindingRpc, Operation op){
		Input in = op.getInput();
		QName opInMsgName = null;

		//RPC binding: the message name is the name of the operation 
		if(isBindingRpc)
			opInMsgName = new QName(op.getName());
		/*
		 * Document binding: construction of the message name:
		 * - if a name is provided for the input message we use this
		 * - if a name is not provided is used the name of the operation
		 */
		else{
			opInMsgName = in.getMessage().getQName();
			if(opInMsgName == null)
				opInMsgName = new QName(op.getName());	
		}
		return opInMsgName;
	}
	
	private QName getOutOpMsgName(boolean isBindingRpc, Operation op){
		Output out = op.getOutput();
		QName outInMsgName = null;

		//RPC binding: the message name is the name of the operation + Response
		if(isBindingRpc)
			outInMsgName = new QName(op.getName()+"Response");
		/*
		 * Document binding: construction of the message name:
		 * - if a name is provided we use this (already saved into opInMsgName
		 * - if a name is not provided is used the name of the operation
		 */
		/*
		 * Document binding: construction of the message name:
		 * - if a name is provided for the output message we use this
		 * - if a name is not provided is used the name of the operation
		 */
		else{
			outInMsgName = out.getMessage().getQName();
			if(outInMsgName == null)
				outInMsgName = new QName(op.getName()+"Response");	
		}
		return outInMsgName;
	}
}